The section describes other errors detected by LCLint that are not
directly related to extra information provided in annotations. Many of
the checks are significantly improved, however, because of the extra
information that is known about the program.
The order in which side effects take place in a C program is
not entirely defined by the code. Certain execution points are known as
sequence points -- a function call (after the arguments have been
evaluated), the end of a full expression (an initializer, expression in
an expression statement, the control expression of an if, switch, while
or do statement, each expression of a for statement, and the expression
in a return statement), and after the first operand or a &&, ||,
? or , operand.
All side effects before a sequence point must be
complete before the sequence point, and no evaluations after the
sequence
point shall have taken place [ANSI, Section 2.1.2.3]. Between sequence
points, side effects and evaluations may take place in any order.
Hence, the order in which expressions or arguments are evaluated is not
specified. Compilers are free to evaluate function arguments and parts
of expressions (that do not contain sequence points) in any order. The
behavior of code that uses a value that is modified by another
expression that is not required to be evaluated before or after the
other use is undefined.
LCLint detects instances where
undetermined order of evaluation produces undefined behavior. If
modifies clauses and globals lists are used, this checking is enabled in
expressions involving function calls. Evaluation order checking is
controlled by the evalorder flag.
When checking systems without
modifies and globals information, evaluation order checking may report
errors when unconstrained functions are called in procedure arguments.
Since LCLint has no annotations to constrain what these functions may
modify, it cannot be guaranteed that the evaluation order is defined if
another argument calls an unconstrained function or uses a global
variable or storage reachable from a parameter to the unconstrained
function. Its best to add modifies and globals clauses to constrain the
unconstrained functions in ways that eliminate the possibility of
undefined behavior. For large legacy systems, this may require too much
effort. Instead, the -evalorderuncon flag
may be used to prevent reporting of undefined behavior due to the order
of evaluation of unconstrained functions.
Figure 19. Evaluation order
A number of control structures that are syntactically legal may indicate
likely bugs in programs. LCLint can detect errors involving likely
infinite loops (Section 10.2.1),
fall through cases and missing cases in switch statements (Section 10.2.2), break statements
within deeply nested loops or switches (Section 10.2.3), clauses of if, while
or for statements that are empty statements or unblocked single
statements (Section 10.2.4) and
incomplete if-else logic (Section
10.2.5). Although any of these may appear in a correct program,
depending on the programming style used they may indicate likely bugs or
style violations that should be detected and eliminated.
LCLint reports an error if it detects a loop that appears to be
infinite. An error is reported for a loop which does not modify any
value used in its condition test inside the body of the loop or in the
condition test itself. This checking is enhanced by modifies clauses
and globals lists since they provide more information about what global
variable may be used in the condition test and what values may be
modified by function calls in the loop body.
Figure 20 shows examples of infinite loops
detected by LCLint. An error is reported for the loop in line 14, since
neither of the values used in the loop condition (x directly and glob1
through the call to f) is modified by the body of the loop. If the
declaration of g is changed to include glob1 in the modifies clause no
error is reported. (In this example, if we assume the annotations are
correct, then the programmer has probably called the wrong function in
the loop body. This isn't surprising, given the horrible choices of
function and variable names!)
If an unconstrained function is called within the loop body, LCLint will assume
that it modifies a value used in the condition test and not report an infinite
loop error, unless infloopsuncon is on. If infloopsuncon is on, LCLint will
report infinite loop errors for loops where there is no explicit modification
of a value used in the condition test, but where they may be an undetected
modification through a call to an unconstrained function (e.g., line 15 in
Figure 20).
The automatic fall-through of C switch statements is almost never the intended
behavior.[25] LCLint detects case statements
with code that may fall through to the next case. The casebreak flag controls
reporting of fall through cases. A single fall through case may be marked by
preceding the case keyword with /*@fallthrough@*/ to indicate explicitly that
execution falls through to this case.
For switches on enum types, LCLint reports an error if a member of the
enumerator does not appear as a case in the switch body (and there is no
default case). (Controlled by misscase.)
An example of switch checking is shown in Figure 21.
There is no syntax provided by C (other than goto) for breaking out of a nested
loop. All break and continue statements act only on the innermost surrounding
loop or switch. This often leads to serious problems[26] when a programmer intends to break the outer loop
or switch instead. LCLint optionally reports errors for break and
continue statements in nested contexts.
- break inside a loop (while or for) that is inside a loop. Controlled by
looploopbreak. To indicate that a break is inside an inner loop, precede the
break by /*@innerbreak@*/.
- break inside a loop that is inside a switch statement. Controlled by
switchloopbreak. To mark the break as a loop break, precede the break by
/*@loopbreak@*/.
- break inside a switch statement that is inside a loop. Controlled by
loopswitchbreak. To mark the break as a switch break, precede the break by
/*@switchbreak@*/.
- break inside a switch inside another switch. Controlled by
switchswitchbreak. To indicate that the break is for the inner switch, use
/*@innerbreak@*/.
Since continue only makes sense within loops, errors are only reported for
continue statements within nested loops. (Controlled by looploopcontinue.) A
safe inner continue may be precede by /*@innercontinue@*/ to suppress error
messages locally. The deepbreak flag sets all nested break and continue
checking flags.
LCLint reports an error if the marker preceding a break is not consistent with
its effect. An error is reported if innerbreak precedes a break that is not
breaking an inner loop, switchbreak precedes a break that is not breaking a
switch, or loopbreak precedes a break that is not breaking a loop.
An empty statement after an if, while or for often indicates a potential bug.
A single statement (i.e., not a compound block) after an if, while or for is
not likely to indicate a bug, but make the code harder to read and edit.
LCLint can report errors for if or loop statements with empty bodies or bodies
that are not compound statements. Separate flags control checking for
statements following an if, while or for:
- [if, while, for]empty --
report errors for empty bodies (e.g., if (x > 3) ;)
- [if, while, for]block --
report errors for non-block bodies (e.g., if (x > 3) x++;)
The if statement checks also apply to the body of the else clause. An ifblock
error is not reported if the body of the else clause is an if statement, to
allow else if chains.
Although it may be perfectly reasonable in many contexts, an if-else chain with
no final else may indicate missing logic or forgetting to check error cases.
If elseifcomplete
is on, LCLint reports errors when an if statement that is the body of an
else clause does not have a matching else clause. For example, the code,
if (x == 0) { return "nil"; }
else if (x == 1) { return "many"; }
produces an error message since the second if has no matching else branch.
LCLint detects errors involving statements with no apparent effects (Section 10.3.1) and statements that
ignore the result of a called function (Section 10.3.2).
LCLint can report errors for statements that have no effect. (Controlled by
noeffect.) Because of modifies clauses, LCLint can detect more errors than
traditional checkers. Unless the noeffectuncon flag is on, errors are not
reported for statements that involve calls to unconstrained functions since the
unconstrained function may cause a modification.
Figure 22. Statements with no effect.
LCLint reports an error when a return value is ignored. Checking may be
controlled based on the type of the return value: retvalint controls reporting
of ignored return values of type int, and retvalbool for return values of type
bool, and retvalothers for all other types. A function statement may be cast
to void to prevent this error from being reported.
Alternate types (Section 8.2.2) can be used to declare functions that return
values that may safely be ignored by declaring the result type to alternately
by void. Several functions in the standard library are specified to
alternately return void to prevent ignored return value errors for standard
library functions (e.g., strcpy) where the result may be safely ignored (see
Apppendix F).
Figure 23 shows example of ignored return value
errors reported by LCLint.
LCLint detects constants, functions, parameters, variables, types, enumerator
members, and structure or union fields that are declared but never used. The
flags
constuse,
fcnuse,
paramuse,
varuse,
typeuse,
enummemuse and
fielduse control whether
unused declaration errors are reported for each kind of declaration.
Errors for exported declarations are reported only if
topuse is on (see Section 10.5).
The /*@unused@*/ annotation can be used before a declaration to indicate that
the item declared need not be used. Unused declaration errors are not reported
for identifiers declared with unused.
LCLint can be used on both complete and partial programs. When checking
complete programs, additional checks can be done to ensure that every
identifier declared by the program is defined and used, and that functions that
do not need to be exported are declared static.
LCLint checks that all declared variables and functions are defined (controlled
by compdef).
Declarations of functions and variables that are defined in an external
library, may be preceded by /*@external@*/ to suppress
undefined declaration errors.
LCLint reports external declarations which are unused (Controlled by topuse).
Which declarations are reported also depends on the declaration use flags (see
Section 10.4).
The partial flag sets
flags for checking a partial system. Top-level unused declarations,
undefined declarations, and unnecessary external names are not reported
if partial is set.
LCLint can report variables and functions that are declared with global scope
(i.e., without using static), that are not used outside the file in which they
are defined. In a stand-alone system, these identifiers should usually be
declared using static to limit their scope. If the exportstatic flag is on,
LCLint will report declarations that could have file scope. It should only be
used when all relevant source files are listed on the LCLint command line;
otherwise, variables and functions may be incorrectly identified as only used
in the file scope since LCLint did not process the other file in which they are
used.
A common practice in C programming styles, is that every function or variable
exported by M.c is declared in M.h. If
the exportheader flag is on, LCLint will report exported declarations in M.c that are not declared in M.h.
The ANSI Standard includes limits on minimum numbers that a conforming compiler
must support. Whether of not a particular compiler exceeds these limits, it is
worth checking that a program does not exceed them so that it may be safely
compiled by other compilers. In addition, exceeding a limit may indicate a
problem in the code (e.g., it is too complex if the control nest depth limit is
exceeded) that should be fixed regardless of the compiler. The following
limits are checked by LCLint. For each limit, the maximum value may be set
from the command line (or locally using a stylized comment). If the
ansilimits
flag is on, all limits are checked with the minimum values of a conforming
compiler.
includenest
Maximum nesting depth of file inclusion (#include). (ANSI minimum is 8)
controlnestdepth
Maximum nesting of compound statements, control structures. (ANSI
minimum is 15)
numenummembers
Number of members in an enum declaration. (ANSI minimum is 127)
numstructfields
Number of fields in a struct or union declaration. (ANSI minimum is 127)
Since human beings themselves are not fully debugged yet, there will be bugs in
your code no matter what you do.
- Chris Mason, Zero-defects memo (Microsoft Secrets, Cusumano
and Selby)
David Evans
Systematic Program Development
evs@larch.lcs.mit.edu